
/*****************************************************************************
一个普通的浇花控制软件
 用了nokia8266屏，库如下
 * https://github.com/bbx10/Adafruit-PCD8544-Nokia-5110-LCD-library/tree/esp8266
 
arduino标准库Time，Adafruit_GFX

 **************************************************************************/

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Time.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>
#include <TimeAlarms.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>


#define RELAY_PIN 0            //继电器IO口 0

WiFiUDP Udp;
WiFiClient client;
HTTPClient httpClient;
String httpString;

String sn_ver="0.20"; //系统版本号
static const char ssid[] = "";         //  wifi SSID (name)
static const char pass[] = "";      // wifi  password

String WXPAI_host="http://pushbear.ftqq.com/sub?sendkey=";    //微信推送API
String WXPAI_key="";     //API KEY
//String WXPAI_key="";
String WXPAI_info=WXPAI_host+WXPAI_key;

String  XZAPI_host = "http://api.seniverse.com/v3/weather/now.json?key=";//心知天气实时API
String  XZAPI_key = "";        //API KEY
String  XZAPI_info=XZAPI_host+XZAPI_key;
String  XZAPI_city = "ip";
String  XZAPI_language = "en";

//心知天气获取连接：[url=https://api.thinkpage.cn/v3/weather/daily.json?key=KEY&location=]https://api.thinkpage.cn/v3/weather/daily.json?key=KEY&location=[/url]城市&language=zh-Hans&unit=c&start=-1&days=5


/*****************************************************************************
nokia5110屏幕实例    引脚对应
引脚  PCB   NOKIA5110接口
pin14 D5   SCK is LCD serial clock (SCLK) - this is pin 14 on Huzzah ESP8266
Pin13 D7 - Data/Command select (D/C) on an Huzzah ESP8266
pin12 D6   MOSI is LCD DIN - this is pin 13 on an Huzzah ESP8266
pin5  D1 - LCD chip select (CS)
pin4  D2 - LCD reset (RST)
 **************************************************************************/
Adafruit_PCD8544 display = Adafruit_PCD8544(12, 5, 4); //

const unsigned long HTTP_TIMEOUT = 2100;               // max respone time from server
const size_t MAX_CONTENT_SIZE = 2048;                   // max size of the HTTP response

static const char ntpServerName[] = "time1.apple.com";   //NTP时间接口 PS:心知不带时间
static const int timeZone = +8;                          // UTC

char play_day=0; //浇花时间控制
char tzName[] = "utc";
char tzTemp[]="10";
char tzCity[]="chongqing";
uint16_t localPort;  // local port to listen for UDP packets




// The type of data that we want to extract from the page -- 我们要从此网页中提取的数据的类型
struct UserData {
  char city[16]; 
  char cnty[16];
  char weather[32];
  char temp[16];
  char code[16];
  String s_code;
  String s_temp;
};
UserData userData;

void printUserData(const struct UserData* userData) {
  Serial.println("Print parsed data :");
  Serial.print("City : ");
  Serial.print(userData->city);
  Serial.print(", \t");
  Serial.print("Country : ");
  Serial.println(userData->cnty);
   
  Serial.print("Weather : ");
  Serial.print(userData->weather);
  Serial.print(",\t");
  Serial.print("Temp : ");
  Serial.print(userData->temp);
  Serial.print(" C");
  Serial.print(",\t");
  Serial.print("code: ");
  Serial.print(userData->code);
  Serial.println("");
  
}

void beginAlarm(){
    int howtime;
    String  tmps;
    howtime=0;
  if(userData.s_temp.toInt()>32)
  {
     play_day=0;
  }
  else if(userData.s_temp.toInt()>25)
  {
      play_day=play_day+3;
  }  
  else if(userData.s_temp.toInt()>10)
  {
      play_day=play_day+2;
  }else
  {
      play_day=play_day+1;
  }
  
 if(play_day>=6)
 {
     play_day=0;
 }else
 {
   send_info_wx(String("今天不浇花"),String("今天天气凉爽就不浇花"));
  }
 
  if(play_day==0)  //合适天气
  {
      if(userData.s_code.toInt()<=3)
       {
            howtime=240;
            send_info_wx(String("开始浇花了"),String("今天天气晴朗浇花4分钟"));
            digitalWrite(RELAY_PIN, 1);
        }
        else if(userData.s_code.toInt()<=9)
       {
            howtime=120;
            send_info_wx(String("开始浇花了"),String("今天天气比较凉爽浇花2分钟"));
            digitalWrite(RELAY_PIN, 1);
        }else if(userData.s_code.toInt()==38)
       {
            howtime=300;
            send_info_wx(String("开始浇花了"),String("今天天气炎热浇花5分钟"));
            digitalWrite(RELAY_PIN, 1);
        }else if(userData.s_code.toInt()<=36)
       {
            howtime=60;
            send_info_wx(String("开始浇花了"),String("今天浇花1分钟"));
            digitalWrite(RELAY_PIN, 1);
        }
        
        if(howtime>0)
        {
            Alarm.timerOnce(howtime, endAlarm);   // end
            Serial.println("Alarm: begin");  
        }
}

}
void endAlarm(){
    digitalWrite(RELAY_PIN, 0);//关闭继电器
    send_info_wx(String("浇花结束啦~~"),String("花花草草们有能健康的成长啦~~")); 
    Serial.println("Alarm: end");    
}

void RepeatsAlarm(){
  get_info_xz(String("en"));
}

// 解析心知数据
bool parse_xz_user_data(String info, struct UserData* userData) {
  // Compute optimal size of the JSON buffer according to what we need to parse.
  //  -- 根据我们需要解析的数据来计算JSON缓冲区最佳大小
  // This is only required if you use StaticJsonBuffer. -- 如果你使用StaticJsonBuffer时才需要
  //  const size_t BUFFER_SIZE = 1024;
 
  // Allocate a temporary memory pool on the stack -- 在堆栈上分配一个临时内存池
  //  StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
  //  -- 如果堆栈的内存池太大，使用 DynamicJsonBuffer jsonBuffer 代替
  // If the memory pool is too big for the stack, use this instead:
StaticJsonBuffer<1024> jsonBuffer;
   const char * content=info.c_str(); //转换为char*
  JsonObject& root = jsonBuffer.parseObject(content);
 
  if (!root.success()) {
    Serial.println("JSON parsing failed!");
    return false;
  }
  //  const char* x = root["results"][0]["location"]["name"];//
  //  Serial.println(x);
  // Here were copy the strings we're interested in -- 复制我们感兴趣的字符串
  strcpy(userData->city, root["results"][0]["location"]["name"]);
  strcpy(userData->cnty, root["results"][0]["location"]["country"]);
  strcpy(userData->weather, root["results"][0]["now"]["text"]);
  strcpy(userData->temp, root["results"][0]["now"]["temperature"]);
  strcpy(userData->code, root["results"][0]["now"]["code"]);
  strcpy(tzName,userData->weather);
  strcpy(tzTemp,userData->temp);
  strcpy(tzCity,userData->city);

  userData->s_code=userData->code[0];
  userData->s_code=userData->s_code+userData->code[1];
  userData->s_temp=userData->temp[0];
  userData->s_temp=userData->s_temp+userData->temp[1];
  return true;
}

// 解析微信数据
bool parse_wx_user_data(String info) {
StaticJsonBuffer<1024> jsonBuffer;
   const char * content=info.c_str(); //转换为char*
  JsonObject& root = jsonBuffer.parseObject(content);
 
  if (!root.success()) {
    Serial.println("JSON parsing failed!");
    return false;
  }
  //  const char* x = root["results"][0]["location"]["name"];//
  //  Serial.println(x);
  // Here were copy the strings we're interested in -- 复制我们感兴趣的字符串
  long code=root["code"];
  if (code==0)
  {
      return 1;
  }
  return 0;
}

// 发送微信提示
bool send_info_wx(String wx_text,String wx_desp) 
{
    httpClient.begin(WXPAI_info+"&text="+wx_text+"&desp="+wx_desp);
    int httpCode = httpClient.GET();
    if (httpCode > 0) 
    {
        if (httpCode == HTTP_CODE_OK) 
        {
            httpString = httpClient.getString();
            Serial.println("wx_info:");
            Serial.println(httpString);
            if(parse_wx_user_data(httpString))
            {
            return 1;
            }
            else
            {
            return 0;
            }
        }
        else
        {
        return 0;
        }
    }
  httpClient.end();
}

// 获取心知天气信息
bool get_info_xz(String language) 
{
    if((language!=String("en"))||(language!=String("cn")))
    {
        httpClient.begin(XZAPI_info+"&location="+XZAPI_city+"&language="+language);
        int httpCode = httpClient.GET();
        if (httpCode > 0) 
        {
            if (httpCode == HTTP_CODE_OK) 
            {
                httpString = httpClient.getString();
                Serial.println("xz_info:");
                Serial.println(httpString);
                parse_xz_user_data(httpString,&userData);
                return 1;
            }
            else
            {
            return 0;
            }
        }
      httpClient.end();
    }
    else
    {
        return 0;
    }
}

void setup()
{

  Serial.begin(115200);

  // Initialize LCD
  display.begin();
  display.setContrast(50);
  display.setTextSize(1);
  display.setTextColor(BLACK);
  display.clearDisplay();
  display.print("Connecting...");
  display.print("ver:");
  display.println(sn_ver);
  display.display();
  pinMode(RELAY_PIN,OUTPUT);//控制初始化
  digitalWrite(RELAY_PIN, 0);
  Serial.println(F("ESP8266 NTP clock on PCD8544 LCD display"));
  Serial.print(F("Connecting to "));
  Serial.println(ssid);
  WiFi.begin(ssid, pass);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(F("."));
  }

  Serial.print(F("IP number assigned by DHCP is "));
  Serial.println(WiFi.localIP());

  // Seed random with values unique to this device
  uint8_t macAddr[6];
  WiFi.macAddress(macAddr);
  uint32_t seed1 =
    (macAddr[5] << 24) | (macAddr[4] << 16) |
    (macAddr[3] << 8)  | macAddr[2];
  randomSeed(WiFi.localIP() + seed1 + micros());
  localPort = random(1024, 65535);

  Serial.println(F("Starting UDP"));
  Udp.begin(localPort);
  Serial.print(F("Local port: "));
  Serial.println(Udp.localPort());
  Serial.println(F("waiting for sync"));
  setSyncProvider(getNtpTime);
  setSyncInterval(5 * 60);


  Alarm.alarmRepeat(19,30,0,beginAlarm);  // 浇花时间设置
  Alarm.timerRepeat(600, RepeatsAlarm);            // 更新天气信息10分钟

  send_info_wx(String("你的浇花姬上线了~"),String("嘤嘤嘤~")); 
  get_info_xz(String("en"));


  Serial.println(httpString);
  analogWrite(15, 256);
}

void loop()
{

  static time_t prevDisplay = 0; // when the digital clock was displayed
  timeStatus_t ts = timeStatus();
  Alarm.delay(1000); // wait one second between clock display
  switch (ts) {
    case timeNeedsSync:
    case timeSet:
      if (now() != prevDisplay) { //update the display only if time has changed
        prevDisplay = now();
        digitalClockDisplay();
        if (ts == timeNeedsSync) {
          Serial.println(F("time needs sync"));
        }
      }
      break;
    case timeNotSet:
      Serial.println(F("Time not set"));
      now();
      break;
    default:
      break;
  }

}

const uint8_t SKINNY_COLON[] PROGMEM = {
  B00000000,
  B00000000,
  B01100000,
  B11110000,
  B11110000,
  B01100000,
  B00000000,
  B00000000,
  B01100000,
  B11110000,
  B11110000,
  B01100000,
  B00000000,
  B00000000,
};

void digitalClockDisplay() {
  tmElements_t tm;
  char *dayOfWeek;

  breakTime(now(), tm);
  dayOfWeek = dayShortStr(tm.Wday);
  // digital clock display of the time
//  Serial.printf("%s %02d %02d %04d %02d:%02d:%02d\r\n",
 //               dayOfWeek, tm.Month, tm.Day, tm.Year + 1970,
//                tm.Hour, tm.Minute, tm.Second);

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextWrap(false);
  display.printf("%s %02d %02d %04d\n",
                 dayOfWeek, tm.Month, tm.Day, tm.Year + 1970);
  display.setTextSize(2);
  display.setCursor(0, 16);
  display.printf("%02d", tm.Hour);
  display.drawBitmap(24, 16, SKINNY_COLON, 4, 14, 1);
  display.setCursor(30, 16);
  display.printf("%02d", tm.Minute);
  display.drawBitmap(54, 16, SKINNY_COLON, 4, 14, 1);
  display.setCursor(60, 16);
  display.printf("%02d", tm.Second);
  display.setTextSize(1);
  display.setCursor(12, 32);
  display.print(tzCity);
  display.setCursor(0, 40);
  display.print(tzName);
  display.display();
  display.setCursor(60, 40);
  display.print(tzTemp);
  display.setCursor(72, 40);
  display.print("C");
  display.display();
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress timeServerIP; // time.nist.gov NTP server address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.print(F("Transmit NTP Request "));
  //get a random server from the pool
  WiFi.hostByName(ntpServerName, timeServerIP);
  Serial.println(timeServerIP);

  sendNTPpacket(timeServerIP);
  uint32_t beginWait = millis();
  while ((millis() - beginWait) < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println(F("Receive NTP Response"));
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + (timeZone * SECS_PER_HOUR);
    }
  }
  Serial.println(F("No NTP Response :-("));
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}
